﻿using System;
using System.Collections.Generic;
using System.Text;

using IVideo;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO;

namespace VideoSDK
{
    public class VideoSDKHC : IVideoSDK
    {
        #region 私有委托
        RealStreamCallback realStreamCallback;
        GetParametersCallback getParametersCallback;
        Transfer485Callback transfer485Callback;
        PaintCallback paintCallback;

        HCNetSDK.RealDataCallBack_V30 realDataCallBack;
        HCNetSDK.SerialDataCallBack Serial485DataHandle;
        HCNetSDK.DrawFun drawFun;
        #endregion

        #region 私有状态
        bool isInitialized = false;
        bool isRecording = false;
        #endregion

        #region 私有变量
        int userID = -1;
        int playHandle = -1;
        int transfer485Handle = -1;
        IntPtr handle;
        int channel = -1;
        uint commond = 0;
        NET_DVR_DEVICEINFO_V30 deviceInfo;
        NET_DVR_CLIENTINFO clientInfo = new NET_DVR_CLIENTINFO();
        AutoResetEvent transfer485Lock = new AutoResetEvent(true);
        #endregion

        #region 私有函数
        private void RealDataCallback(int playHandle, uint dataType, IntPtr buffer, uint bufferSize, IntPtr user)
        {
            if (this.playHandle < 0 || this.playHandle != playHandle)
                return;

            IntPtr tempBuffer = Marshal.AllocHGlobal((int)bufferSize);
            HCNetSDK.CopyMemory(tempBuffer, buffer, bufferSize);

            if (dataType == 1)
            {
                if (this.realStreamCallback != null)
                    this.realStreamCallback(tempBuffer, bufferSize);

            }
            else if (dataType == 2)
            {
                if (this.realStreamCallback != null)
                    this.realStreamCallback(tempBuffer, bufferSize);
            }
        }

        private void Serial485DataCall(int lSerialHandle, IntPtr pRecvDataBuffer, uint dwBufSize, uint dwUser)
        {
            if (this.transfer485Handle < 0 || lSerialHandle < 0)
                return;

            if (this.transfer485Handle != lSerialHandle)
                return;

            byte[] Message = new byte[dwBufSize];
            Marshal.Copy(pRecvDataBuffer, Message, 0, Convert.ToInt32(dwBufSize));

            if (this.transfer485Callback != null)
                transfer485Callback(Message);
        }

        private void PaintCall(int lRealHandle, IntPtr hDc, uint dwUser)
        {
            if (this.IsOpened && this.paintCallback != null)
                this.paintCallback(hDc);
        }

        bool PTZControl(uint commond)
        {
            this.CheckLogined(true, "StopRecord");
            this.CheckOpened(true, "StopRecord");
            this.commond = commond;

            return HCNetSDK.NET_DVR_PTZControl(this.playHandle, commond, 0);
        }

        bool PTZControl(uint commond, bool stop, byte speed)
        {
            this.CheckLogined(true, "StopRecord");
            this.CheckOpened(true, "StopRecord");
            this.commond = commond;

            return HCNetSDK.NET_DVR_PTZControlWithSpeed(this.playHandle, commond, 0, speed);
        }

        bool PTZControlStop()
        {
            this.CheckLogined(true, "StopRecord");
            this.CheckOpened(true, "StopRecord");
            if (this.commond == 0) return false;

            return HCNetSDK.NET_DVR_PTZControl(this.playHandle, this.commond, 1);
        }

        void CheckLogined(bool state, string function)
        {
            if (state && (!this.isInitialized || this.userID < 0))
                throw new InvalidOperationException("User hasn't logined encoder!|" + function);
            else if (!state && this.isInitialized && this.userID >= 0)
                throw new InvalidOperationException("User has logined encoder!|" + function);
        }

        void CheckOpened(bool state, string function)
        {
            if (state && this.playHandle < 0)
                throw new InvalidOperationException("Video hasn't been opened!|" + function);
            else if (!state && this.playHandle >= 0)
                throw new InvalidOperationException("Video has been opened!|" + function);
        }

        void CheckRecording(bool state, string function)
        {
            if (state && !this.isRecording)
                throw new InvalidOperationException("Video recording hasn't been started!|" + function);
            else if (!state && this.isRecording)
                throw new InvalidOperationException("Video recording has been started!|" + function);
        }

        void CheckTransfer485(bool state, string function)
        {
            if (state && this.transfer485Handle < 0)
                throw new InvalidOperationException("Transfer 485 hasn't been opened!|" + function);
            else if (!state && this.transfer485Handle >= 0)
                throw new InvalidOperationException("Transfer 485 has been started!|" + function);
        }

        #endregion

        #region IVideoSDK 成员

        #region 事件委托
        public RealStreamCallback RealStreamCallback
        {
            set { realStreamCallback = value; }
        }

        public GetParametersCallback GetParametersCallback
        {
            set { getParametersCallback = value; }
        }

        public Transfer485Callback Transfer485Callback
        {
            set { transfer485Callback = value; }
        }

        public PaintCallback PaintCallback
        {
            set { paintCallback = value; }
        }

        #endregion

        #region 状态变量
        public bool IsLogined
        {
            get { return this.userID >= 0; }
        }

        public bool IsOpened
        {
            get { return this.userID >= 0 && this.playHandle >= 0; }
        }

        public bool IsRecording
        {
            get { return isRecording; }
        }

        public bool IsTransferOpened
        {
            get { return this.transfer485Handle >= 0; }
        }
        #endregion

        #region 公有函数
        #region 视频管理
        public bool LoginEncoder(string ip, ushort port, string user, string password)
        {
            this.CheckLogined(false, "LoginEncoder");

            if (!this.isInitialized)
            {
                this.isInitialized = HCNetSDK.NET_DVR_Init();

                this.userID = -1;
            }

            if (this.isInitialized)
            {
                this.userID = HCNetSDK.NET_DVR_Login_V30(ip, port, user, password, out deviceInfo);
            }

            if (this.userID < 0)
                HCNetSDK.NET_DVR_Cleanup();


            return (this.userID >= 0);
        }

        public bool LogoutEncoder()
        {
            this.CheckLogined(true, "LogoutEncoder");

            if (this.transfer485Handle >= 0)
                if (HCNetSDK.NET_DVR_SerialStop(this.transfer485Handle))
                    this.transfer485Handle = -1;

            if (HCNetSDK.NET_DVR_Logout_V30(this.userID))
            {
                this.userID = -1;

                this.isInitialized = !(HCNetSDK.NET_DVR_Cleanup());

                if (this.isInitialized)
                    throw new InvalidOperationException("Cleanup Failed!|LogoutEncoder");

                return true;
            }
            else
                return false;
        }

        public bool OpenVideoDecode(byte channel, IntPtr handle)
        {
            this.CheckLogined(true, "OpenVideo");
            this.CheckOpened(false, "OpenVideo");

            if (channel <= 0 || channel > this.deviceInfo.byChanNum)
                throw new ArgumentOutOfRangeException("Channel is out of range!|OpenVideo");

            this.realDataCallBack = new HCNetSDK.RealDataCallBack_V30(RealDataCallback);
            this.clientInfo.hPlayWnd = handle;
            this.clientInfo.lChannel = channel;
            this.clientInfo.lLinkMode = ((int)StreamType.Master) | ((int)LinkMode.TCP);

            this.playHandle = HCNetSDK.NET_DVR_RealPlay_V30(this.userID, ref this.clientInfo, realDataCallBack, IntPtr.Zero, true);

            this.drawFun = new HCNetSDK.DrawFun(this.PaintCall);
            HCNetSDK.NET_DVR_RigisterDrawFun(this.playHandle, this.drawFun, 0);

            if (this.playHandle < 0)
                return false;
            else
            {
                this.handle = handle;
                this.channel = channel;
                return true;
            }
        }

        public bool OpenVideo(byte channel, IntPtr handle)
        {
            this.CheckLogined(true, "OpenVideo");
            this.CheckOpened(false, "OpenVideo");

            if (channel <= 0 || channel > this.deviceInfo.byChanNum)
                throw new ArgumentOutOfRangeException("Channel is out of range!|OpenVideo");

            this.clientInfo.hPlayWnd = handle;
            this.clientInfo.lChannel = channel;
            this.clientInfo.lLinkMode = ((int)StreamType.Master) | ((int)LinkMode.TCP);

            this.playHandle = HCNetSDK.NET_DVR_RealPlay(this.userID, ref this.clientInfo);

            this.drawFun = new HCNetSDK.DrawFun(this.PaintCall);
            HCNetSDK.NET_DVR_RigisterDrawFun(this.playHandle, this.drawFun, 0);

            if (this.playHandle < 0)
                return false;
            else
            {
                this.handle = handle;
                this.channel = channel;
                return true;
            }
        }

        public bool CloseVideo()
        {
            this.CheckLogined(true, "CloseVideo");
            this.CheckOpened(true, "CloseVideo");

            if (this.isRecording)
                this.isRecording = !HCNetSDK.NET_DVR_StopSaveRealData(this.playHandle);

            bool result = HCNetSDK.NET_DVR_StopRealPlay(this.playHandle);

            if (result)
            {
                this.playHandle = -1;
                this.channel = -1;
            }

            return result;
        }

        public bool SnapVideo(string file)
        {
            this.CheckLogined(true, "SnapVideo");
            this.CheckOpened(true, "SnapVideo");

            return HCNetSDK.NET_DVR_CapturePicture(this.playHandle, file);
        }

        public bool StartRecord(string file)
        {
            this.CheckLogined(true, "StartRecord");
            this.CheckOpened(true, "StartRecord");
            this.CheckRecording(false, "StopRecord");

            FileInfo FI = new FileInfo(file);

            if (!FI.Directory.Exists) FI.Directory.Create();

            return this.isRecording = HCNetSDK.NET_DVR_SaveRealData(this.playHandle, file);
        }

        public bool StopRecord()
        {
            this.CheckLogined(true, "StopRecord");
            this.CheckOpened(true, "StopRecord");
            this.CheckRecording(true, "StopRecord");

            return this.isRecording = !HCNetSDK.NET_DVR_StopSaveRealData(this.playHandle);
        }
        #endregion

        #region 云台控制
        public bool PanLeft(byte speed)
        {
            return this.PTZControl(HCNetSDK.PAN_LEFT, false, speed);
        }

        public bool PanRight(byte speed)
        {
            return this.PTZControl(HCNetSDK.PAN_RIGHT, false, speed);
        }

        public bool TiltUp(byte speed)
        {
            return this.PTZControl(HCNetSDK.TILT_UP, false, speed);
        }

        public bool TiltDown(byte speed)
        {
            return this.PTZControl(HCNetSDK.TILT_DOWN, false, speed);
        }

        public bool PanLeftTiltUp(byte speed)
        {
            return this.PTZControl(HCNetSDK.UP_LEFT, false, speed);
        }

        public bool PanLeftTiltDown(byte speed)
        {
            return this.PTZControl(HCNetSDK.DOWN_LEFT, false, speed);
        }

        public bool PanRightTiltUp(byte speed)
        {
            return this.PTZControl(HCNetSDK.UP_RIGHT, false, speed);
        }

        public bool PanRightTiltDown(byte speed)
        {
            return this.PTZControl(HCNetSDK.UP_LEFT, false, speed);
        }
        #endregion

        #region 镜头控制
        public bool ZoomOut()
        {
            return this.PTZControl(HCNetSDK.ZOOM_OUT);
        }

        public bool ZoomIn()
        {
            return this.PTZControl(HCNetSDK.ZOOM_IN);
        }

        public bool FocusNear()
        {
            return this.PTZControl(HCNetSDK.FOCUS_NEAR);
        }

        public bool FocusFar()
        {
            return this.PTZControl(HCNetSDK.FOCUS_FAR);
        }

        public bool IrisClose()
        {
            return this.PTZControl(HCNetSDK.IRIS_CLOSE);
        }

        public bool IrisOpen()
        {
            return this.PTZControl(HCNetSDK.IRIS_OPEN);
        }

        public bool PTZStop()
        {
            return this.PTZControlStop();
        }
        #endregion

        #region 透明通道
        public bool OpenTransfer485()
        {
            this.CheckLogined(true, "OpenTransfer485");
            this.CheckTransfer485(false, "OpenTransfer485");

            this.Serial485DataHandle = new HCNetSDK.SerialDataCallBack(Serial485DataCall);

            this.transfer485Handle = HCNetSDK.NET_DVR_SerialStart(this.userID, 2, this.Serial485DataHandle, 0);

            return this.transfer485Handle >= 0;
        }

        public bool SendTransfer485(byte[] Message)
        {
            this.CheckLogined(true, "SendTransfer485");
            this.CheckTransfer485(true, "SendTransfer485");

            if (Message == null || Message.Length == 0)
                throw new ArgumentOutOfRangeException("Message is null or length of message is zero!|SendTransfer485");

            if (this.channel <= 0 || this.channel > this.deviceInfo.byChanNum)
                throw new ArgumentOutOfRangeException("Channel is out of range!|SendTransfer485");

            int bufferSize = -1;
            IntPtr pBuffer = IntPtr.Zero;

            lock (Message)
            {
                bufferSize = Message.Length;
                pBuffer = Marshal.AllocHGlobal(bufferSize);
                Marshal.Copy(Message, 0, pBuffer, bufferSize);
            }

            bool result = HCNetSDK.NET_DVR_SerialSend(this.transfer485Handle, this.channel, pBuffer, Convert.ToUInt32(bufferSize));

            Marshal.FreeHGlobal(pBuffer);

            transfer485Lock.Set();

            return result;
        }

        public bool CloseTransfer485()
        {
            this.CheckLogined(true, "CloseTransfer485");
            this.CheckTransfer485(true, "CloseTransfer485");

            bool result = HCNetSDK.NET_DVR_SerialStop(this.transfer485Handle);

            if (result)
            {
                this.transfer485Handle = -1;
                this.Serial485DataHandle = null;
            }

            return result;
        }
        #endregion
        #endregion

        #endregion
    }
}
